using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.IO;
using System.Xml;
using Stormwind.Core.Configuration;

namespace Stormwind.Core.Data {
	/// <summary>
	/// Data broker to be used to convert DataTable to Xml that the parser can understand.
	/// </summary>
	public class DataBroker {

		/// <summary>
		/// Returns data from the currently configured Data Broker.
		/// </summary>
		/// <param name="def">Data Definition of the report.</param>
		/// <returns>Returns data in Xml format.</returns>
		public static string GetData(DataDefinition def) {
			DataTable dt = StormwindContainer.Instance.DataBroker.GetData(def);
			return ConvertToXml(def, dt);
		}

		#region Convert to Xml
		/// <summary>
		/// Converts the HQL Result to a Xml format abiding to the rules specified in the 
		/// data definition.
		/// </summary>
		/// <param name="def">Data Definition.</param>
		/// <param name="result">Result from the HQL Query.</param>
		/// <returns>Returns the Xml.</returns>
		/// <remarks>
		/// <![CDATA[
		/// <?xml version="1.0" encoding="utf-8" ?>
		/// <Resultset>
		/// ***DATA TABLES***
		/// </Resultset>
		/// ]]>
		/// </remarks>
		protected static string ConvertToXml(DataDefinition def, DataTable result) {
			StringWriter sw = new StringWriter();
			XmlTextWriter xw = new XmlTextWriter(sw);

			xw.WriteStartDocument();
			{
				xw.WriteStartElement("ResultSet");
				{
					int index = 0;
					xw.WriteAttributeString("RecordCount", result.Rows.Count.ToString());
					WriteGrouping(def, result, xw, index, new Dictionary<FieldDefinition, object>());
				}
				xw.WriteEndElement();
			}
			xw.WriteEndDocument();

			return sw.ToString();
		}

		/// <summary>
		/// Write a xml grouping.
		/// </summary>
		/// <param name="def">Data Definition.</param>
		/// <param name="result">Result to write.</param>
		/// <param name="xw">XmlTextWriter.</param>
		/// <param name="index">Current level of grouping.</param>
		/// <param name="filters">Filters to use.</param>
		///	<remarks>
		/// <![CDATA[
		///<DataTable name="Customer">
		///  <DataItems>
		///    <DataItem>
		///      <field type="integer" name="Id">1</field>
		///      <field type="string" name="Name">Test Customer Inc.</field>
		///      ***Inner Tables***
		///    <DataItem>
		///  <DataItems>
		///</DataTable>
		/// ]]>
		/// </remarks>
		protected static void WriteGrouping(DataDefinition def, DataTable result, XmlTextWriter xw, int index,
																	Dictionary<FieldDefinition, object> filters) {
			GroupingDefinition gd = def.Groupings[index];

			xw.WriteStartElement("DataTable");
			{
				xw.WriteAttributeString("name", gd.Field.DataSource.ClassName);
				xw.WriteStartElement("DataItems");
				{
					result.DefaultView.RowFilter = GetFilters(filters);
					DataTable filtered = result.DefaultView.ToTable();
					object lastValue = string.Empty;
					foreach (DataRow dr in filtered.Rows) {
						if (!lastValue.ToString().Equals(dr[gd.Field.FullPropName].ToString(), StringComparison.InvariantCultureIgnoreCase)) {
							lastValue = dr[gd.Field.FullPropName];
							xw.WriteStartElement("DataItem");
							{
								foreach (FieldDefinition fd in def.Fields) {
									if (index + 1 == def.Groupings.Count ||
												fd.DataSource.ClassName.Equals(gd.Field.DataSource.ClassName, StringComparison.InvariantCultureIgnoreCase)) {
										xw.WriteStartElement("Field");
										{
											xw.WriteAttributeString("type", fd.PropertyType.ToString());
											xw.WriteAttributeString("name", fd.PropertyName);
											xw.WriteAttributeString("class", fd.DataSource.ClassName);
											if (fd.PropertyType == DataTypes.String) {
												xw.WriteCData(dr[fd.FullPropName].ToString());
											}
											else {
												xw.WriteString(dr[fd.FullPropName].ToString());
											}
										}
										xw.WriteEndElement();
									}
								}
								Dictionary<FieldDefinition, object> newFilters = new Dictionary<FieldDefinition, object>(filters);
								newFilters.Add(gd.Field, lastValue);
								if (index + 1 < def.Groupings.Count) {
									WriteGrouping(def, filtered, xw, index + 1, newFilters);
								}
							}
							xw.WriteEndElement();
						}
					}
				}
				xw.WriteEndElement();
			}
			xw.WriteEndElement();
		}

		/// <summary>
		/// Gets the filter string.
		/// </summary>
		/// <param name="filters">Filters.</param>
		/// <returns>Returns the filters string.</returns>
		protected static string GetFilters(Dictionary<FieldDefinition, object> filters) {
			StringBuilder sb = new StringBuilder();
			int i = 1;
			foreach (KeyValuePair<FieldDefinition, object> filter in filters) {
				sb.AppendFormat("{0}.{1} = '{2}'", filter.Key.DataSource.ClassName, filter.Key.PropertyName, filter.Value);
				if (i < filters.Count) {
					sb.Append(" and ");
				}
				i++;
			}

			return sb.ToString();
		}
		#endregion Convert to Xml
	}
}
